/**
 * 无向图，可以任意切桥，任意连边
 * 最大化最后两个连通块的点数之积
 * 显然是要将N个点分成两个接近的数量，但是有桥的限制，不能随意分。
 * 
 * 首先断开所有的桥，然后求出所有连通块的点数，连通块的数量记作M
 * 于是题目变为了M个数分为两堆，尽可能接近。显然是一个背包，但是01背包会T，因为M最大可到N
 * 
 * 注意到总共有N个点，因此不同连通块的点数最多有sqrt(N)个，因此可以用多重背包
 * 令Ci为点数是i的连通块的数量，数组C的长度最多是sqrt(N)。
 * 对C做一个多重背包即可
 */
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;

using llt = long long;
using vi = vector<int>;
using vll = vector<llt>;
struct CutBridgeTarjan{

using weight_t = int;
using vi = vector<int>;

struct edge_t{
    int from;
	int to;
	weight_t w;
	edge_t(int a, int b, int c):from(a),to(b),w(c){}
};

int N;
vector<edge_t> Edges; // 0-index
vector<vi> G; // 1-index

/// 点数和边数
void init(int n, int m=0){
    G.assign((N = n) + 1, {});
	Edges.clear();
	Edges.reserve(m + m);
}

void mkBiEdge(int a, int b, weight_t w=0){
	G[a].emplace_back(Edges.size());
	Edges.emplace_back(a, b, w);
	G[b].emplace_back(Edges.size());
	Edges.emplace_back(b, a, w);
}

void Trajan(){
    Dfn.assign(N + 1, TimeStamp = StackTop = 0);
    Low.assign(N + 1, 0);	
	IsCut.assign(N + 1, 0);
	AddBlocks.assign(N + 1, 0);

    IsVisited.assign(Edges.size(), 0);
    Stack.assign(Edges.size(), 0);
	IsBridge.assign(Edges.size(), BridgeCnt = 0);

	for(int i=1;i<=N;++i)if(0==Dfn[i])dfs(i, i);
	return;
}

void dfs(int u, int p){
	int v, son = 0;
	Dfn[u] = Low[u] = ++TimeStamp;

	for(auto i : G[u])if(not IsVisited[i]){
        IsVisited[i] = IsVisited[i ^ 1] = 1;

		if(0 == Dfn[v = Edges[i].to]){
			++son;
			dfs(v, u);
			if(Low[v] < Low[u]) Low[u] = Low[v];

			/// 桥
			if(Dfn[u] < Low[v]){
                IsBridge[i] = IsBridge[i ^ 1] = 1;
				++BridgeCnt;
			}

			/// 割点条件2
			if(u != p and Dfn[u] <= Low[v]){
				IsCut[u] = 1;
				++AddBlocks[u];
			}
		}else if(Dfn[v] < Low[u]){
			Low[u] = Dfn[v];
		}
	}

	/// 割点条件1
	if(u == p and son > 1) IsCut[u] = 1;
	if(u == p) AddBlocks[u] = son - 1;
	return;
}

vi Stack;
int StackTop;
int TimeStamp;
vi Dfn, Low;

vi IsVisited;
vi IsBridge;
int BridgeCnt;

vi AddBlocks;
vi IsCut;

};

struct UFSet{ // 并查集

vector<int> father;
vector<int> sz;

void init(int n){father.assign(n+1, 0);for(int i=1;i<=n;++i)father[i]=i;sz.assign(n + 1, 1);}

int find(int x){return father[x]==x?x:father[x]=find(father[x]);}

void unite(int x, int y){
    x = find(x);
    y = find(y);
    if(sz[x] <= sz[y]) father[y] = x, sz[x] += sz[y];
    else father[x] = y, sz[y] += sz[x];
}

};

void f01(vector<int> & d, int maxv, int cost){
    for(int v=maxv;v>=cost;--v){
        d[v] |= d[v - cost];
    }
}

void fc(vector<int> & d, int maxv, int cost){
    for(int v=cost;v<=maxv;++v){
        d[v] |= d[v - cost];
    }
}

void f(vector<int> & d, int maxv, int cost, int cnt){
    if(cost * cnt >= maxv) return fc(d, maxv, cost);

    for(int k=1;k<cnt;k<<=1){
        f01(d, maxv, k*cost);
        cnt -= k;
    }

    if(cnt) f01(d, maxv, cnt * cost);
}

int N;
int M;
UFSet U;
CutBridgeTarjan T;

llt proc(){
    T.Trajan();
    U.init(N);

    for(int i=0;i<T.Edges.size();i+=2){
        if(not T.IsBridge[i]){
            if(U.find(T.Edges[i].from) != U.find(T.Edges[i].to)){
                U.unite(T.Edges[i].from, T.Edges[i].to);
            }
        }
    }

    map<int, int> Map;
    for(int i=1;i<=N;++i){
        if(U.find(i) == i){
            Map[U.sz[i]] += 1;
        }
    }

    int maxv = N;
    vector<int> D(maxv + 1, 0);
    D[0] = 1;
    for(const auto & p : Map){
        int cost = p.first;
        int cnt = p.second;
        f(D, maxv, cost, cnt);
    }

    llt ans = 0;
    for(int i=1;i<maxv;++i){
        if(D[i]){
            assert(D[maxv - i]);
            ans = max(ans, (0LL + i) * (maxv - i));
        }
    }
    return ans;
}

void work(){
    cin >> N >> M;
    T.init(N, M);
    for(int a,b,i=0;i<M;++i){
        cin >> a >> b;
        T.mkBiEdge(a, b);
    }
    cout << proc() << endl;
    return;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("z.txt", "r", stdin);
#endif
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int nofkase = 1;
    // cin >> nofkase;
    while(nofkase--) work();
    return 0;
}